package cn.signalpha.modules.pay.granter;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.signalpha.common.cache.ConfigCache;
import cn.signalpha.common.cache.ConfigKeys;
import cn.signalpha.common.dto.ConfigDTO;
import cn.signalpha.common.utils.SnowflakeUtil;
import cn.signalpha.core.http.util.HttpUtil;
import cn.signalpha.core.log.exception.ServiceException;
import cn.signalpha.core.tool.jackson.JsonUtil;
import cn.signalpha.core.tool.utils.*;
import cn.signalpha.modules.pay.dto.*;
import cn.signalpha.modules.pay.provider.IPayGranter;
import cn.signalpha.modules.pay.vo.RefundVO;
import cn.signalpha.modules.pay.vo.SettleOrderVO;
import cn.signalpha.modules.pay.vo.SettleUserVO;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 阿里支付
 *
 * @author signalpha
 */
@Slf4j
@Component
@AllArgsConstructor
public class JoinPayFastGranter implements IPayGranter {

	private final SnowflakeUtil snowflakeUtil;

	@Override
	public String getPayVO(PayDTO payDTO) {
		payDTO.setNotifyUrl(payDTO.getNotifyUrl().concat("/join/fast"));
		return joinPayFast(payDTO);
	}

	@Override
	public String verifyNotify(Map<String, String> params) {
		ConfigDTO.JoinPayDTO config = ConfigCache.getObject(ConfigKeys.JOIN_PAY, ConfigDTO.JoinPayDTO.class);
		String paramsStr = SignUtil.getSortedString(params.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
		return RSAUtil.verify(paramsStr, config.getPublicJoinKey(), params.get("sign"), false) ? StringPool.TRUE : StringPool.FALSE;
	}

	@Override
	@SneakyThrows
	public RefundVO refund(RefundDTO refundDTO) {
		return RefundVO.builder().code(StringPool.NO).build();
	}

	@Override
	public SettleUserVO settleUser(SettleUserDTO settleUserDTO) {
		return SettleUserVO.builder().code(StringPool.NO).msg("该支付未实现分账").build();
	}

	@Override
	public SettleOrderVO settleOrder(SettleOrderDTO settleOrderDTO) {
		return SettleOrderVO.builder().code(StringPool.NO).msg("该支付未实现分账").build();
	}

	/**
	 * 短息签约 - 发送短信
	 */
	public String signSms(SignSmsDTO signSmsDTO) {
		String orderNo = snowflakeUtil.snowflakeId();
		ConfigDTO.JoinPayDTO config = ConfigCache.getObject(ConfigKeys.JOIN_PAY, ConfigDTO.JoinPayDTO.class);
		if(StringUtil.isAnyBlank(config.getAesKey(), config.getPrivateKey(), config.getPublicKey(), config.getPublicJoinKey())){
			throw new ServiceException("快捷支付存在空配置，请联系系统管理员");
		}

		Map<String, Object> dataMap = new HashMap<>();
		dataMap.put("mch_order_no", orderNo);
		dataMap.put("order_amount", "0.01");
		dataMap.put("mch_req_time", DateUtil.now());
		dataMap.put("id_type", signSmsDTO.getIdCardType());
		dataMap.put("payer_name", AESUtil.Aes256Encode(signSmsDTO.getName(), config.getAesKey()));
		dataMap.put("id_no", AESUtil.Aes256Encode(signSmsDTO.getIdCard(), config.getAesKey()));
		dataMap.put("bank_card_no", AESUtil.Aes256Encode(signSmsDTO.getBankCardNo(), config.getAesKey()));
		dataMap.put("mobile_no", AESUtil.Aes256Encode(signSmsDTO.getMobile(), config.getAesKey()));
		if(StringUtil.isNotBlank(signSmsDTO.getExpireDate())){
			dataMap.put("expire_date", AESUtil.Aes256Encode(signSmsDTO.getExpireDate(), config.getAesKey()));
		}
		if(StringUtil.isNotBlank(signSmsDTO.getCvv())){
			dataMap.put("cvv", AESUtil.Aes256Encode(signSmsDTO.getCvv(), config.getAesKey()));
		}

		Map<String, Object> params = new HashMap<>();
		params.put("mch_no", config.getMerchantNo());
		params.put("method", "fastPay.agreement.signSms");
		params.put("version", "1.0");
		params.put("rand_str", RandomUtil.randomString(32));
		params.put("sign_type", "2");
		params.put("sec_key", RSAUtil.encryptByPublicKey(config.getPublicJoinKey(), config.getAesKey()));
		params.put("data", JsonUtil.toJson(dataMap));

		String signStr = SignUtil.getSortedString(params);
		params.put("sign", RSAUtil.sign(signStr, config.getPrivateKey(), false));

		String resultStr = HttpUtil.post("https://api.joinpay.com/fastpay", params);
		Map<String, Object> resultMap = JsonUtil.toMap(resultStr);
		if(!"JS000000".equals(String.valueOf(resultMap.get("biz_code")))){
			throw new ServiceException(StringUtil.format("快捷支付短信发送失败[{}]", resultMap.get("biz_msg")));
		}
		Map<String, Object> resultDataMap = JsonUtil.toMap(String.valueOf(resultMap.get("data")));
		if(ObjectUtil.isNotEmpty(resultDataMap.get("err_code"))){
			throw new ServiceException(StringUtil.format("快捷支付短信发送失败[{}]", resultDataMap.get("err_msg")));
		}
		return orderNo;
	}

	/**
	 * 短息签约 - 进行签约
	 */
	public boolean sign(SignDTO signDTO) {
		ConfigDTO.JoinPayDTO config = ConfigCache.getObject(ConfigKeys.JOIN_PAY, ConfigDTO.JoinPayDTO.class);
		if(StringUtil.isAnyBlank(config.getAesKey(), config.getPrivateKey(), config.getPublicKey(), config.getPublicJoinKey())){
			throw new ServiceException("快捷支付存在空配置，请联系系统管理员");
		}

		Map<String, Object> dataMap = new HashMap<>();
		dataMap.put("mch_order_no", signDTO.getOrderNo());
		dataMap.put("sms_code", signDTO.getSmsCode());

		Map<String, Object> params = new HashMap<>();
		params.put("mch_no", config.getMerchantNo());
		params.put("method", "fastPay.agreement.smsSign");
		params.put("version", "1.0");
		params.put("rand_str", RandomUtil.randomString(32));
		params.put("sign_type", "2");
		params.put("sec_key", RSAUtil.encryptByPublicKey(config.getPublicJoinKey(), config.getAesKey()));
		params.put("data", JsonUtil.toJson(dataMap));
		params.put("sign", RSAUtil.sign(SignUtil.getSortedString(params), config.getPrivateKey(), false));

		String resultStr = HttpUtil.post("https://api.joinpay.com/fastpay", params);
		Map<String, Object> resultMap = JsonUtil.toMap(resultStr);
		if(!"JS000000".equals(String.valueOf(resultMap.get("biz_code")))){
			throw new ServiceException(StringUtil.format("快捷支付短信签约失败[{}]", resultMap.get("biz_msg")));
		}
		Map<String, Object> resultDataMap = JsonUtil.toMap(String.valueOf(resultMap.get("data")));
		if(ObjectUtil.isNotEmpty(resultDataMap.get("err_code"))){
			throw new ServiceException(StringUtil.format("快捷支付短信签约失败[{}]", resultDataMap.get("err_msg")));
		}
		return true;
	}


	/**
	 * 无短信支付
	 */
	private String joinPayFast(PayDTO payDTO) {
		ConfigDTO.JoinPayDTO config = ConfigCache.getObject(ConfigKeys.JOIN_PAY, ConfigDTO.JoinPayDTO.class);
		Map<String, Object> dataMap = new HashMap<>();
		dataMap.put("mch_order_no", payDTO.getPayNo());
		dataMap.put("order_amount", NumberUtil.decimalFormatMoney(Double.parseDouble(payDTO.getPayMoney())));
		dataMap.put("mch_req_time", DateUtil.now());
		dataMap.put("order_desc", payDTO.getBody());
		dataMap.put("callback_url", payDTO.getNotifyUrl());
		dataMap.put("callback_param", payDTO.getNotifyParams());
		dataMap.put("bank_card_no", AESUtil.Aes256Encode(payDTO.getBankCardNo(), config.getAesKey()));

		Map<String, Object> params = new HashMap<>();
		params.put("mch_no", config.getMerchantNo());
		params.put("method", "fastPay.agreement.pay");
		params.put("version", "1.0");
		params.put("rand_str", RandomUtil.randomString(32));
		params.put("sign_type", "2");
		params.put("sec_key", RSAUtil.encryptByPublicKey(config.getPublicJoinKey(), config.getAesKey()));
		params.put("data", JsonUtil.toJson(dataMap));
		params.put("sign", RSAUtil.sign(SignUtil.getSortedString(params), config.getPrivateKey(), false));

		String resultStr = HttpUtil.post("https://api.joinpay.com/fastpay", params);
		Map<String, Object> resultMap = JsonUtil.toMap(resultStr);
		if(!"JS000000".equals(String.valueOf(resultMap.get("biz_code")))){
			throw new ServiceException(StringUtil.format("快捷支付拉起支付失败[{}]", resultMap.get("biz_msg")));
		}
		Map<String, Object> resultDataMap = JsonUtil.toMap(String.valueOf(resultMap.get("data")));
		if(ObjectUtil.isNotEmpty(resultDataMap.get("err_code"))){
			throw new ServiceException(StringUtil.format("快捷支付拉起支付失败[{}]", resultDataMap.get("err_msg")));
		}
		return String.valueOf(resultDataMap.get("jp_order_no"));
	}

	/**
	 * 签名、验签的工具类
	 */
	private static class SignUtil {
		public final static String SIGN_SEPARATOR = "&";//分隔符
		public final static String SIGN_EQUAL = "=";//等于号
		public final static String SIGN_KEY_PARAM_NAME = "key";
		public final static List<String> NOT_SIGN_PARAM = Arrays.asList("sign","aesKey","aes_key","sec_key"); //不参与签名/验签的参数

//		/**
//		 * 验证签名
//		 * @param response
//		 * @param key
//		 * @return
//		 */
//		public static boolean verify(Response response, String key) throws Exception {
//			String signStr = getSortedString(response);
//			if(SignTypeEnum.MD5.getValue().equals(response.getSign_type())){
//				signStr = HEXUtil.encode(genMD5Sign(signStr, key), true);
//				if(signStr.equals(response.getSign())){
//					return true;
//				}else{
//					return false;
//				}
//			}else if(SignTypeEnum.RSA.getValue().equals(response.getSign_type())){
//				return RSAUtil.verify(signStr, key, HEXUtil.decode(response.getSign()), true);
//			}else{
//				return false;
//			}
//		}
//
//		/**
//		 * 生成签名
//		 * @param request
//		 * @param key
//		 * @return
//		 */
//		public static void sign(Request request, String key) throws Exception {
//			if(StringUtil.isEmpty(request)){
//				return;
//			}else if(StringUtil.isEmpty(request.getMch_id()) || StringUtil.isEmpty(request.getSign_type())){
//				request.setSign("");
//				return;
//			}
//
//			if(StringUtil.isEmpty(request.getRand_str())){
//				request.setRand_str(RandomUtil.get32LenStr());
//			}
//
//			String signStr = getSortedString(request);
//
//			if(SignTypeEnum.MD5.getValue().equals(request.getSign_type())){
//				signStr = HEXUtil.encode(genMD5Sign(signStr, key), true);
//			}else if(SignTypeEnum.RSA.getValue().equals(request.getSign_type())){
//				signStr = HEXUtil.encode(RSAUtil.sign(signStr, key, true));
//			}else{
//				//抛出签名失败的异常
//				throw new JPException("签名失败，未预期的签名类型："+request.getSign_type());
//			}
//			request.setSign(signStr);
//		}

		protected static String getSortedString(Map<String, Object> params) {
			Map<String, String> map = new HashMap<>();
			for (Map.Entry<String, Object> entry : params.entrySet()) {
				//不参与签名或验签的参数直接跳过
				if(NOT_SIGN_PARAM.contains(entry.getKey())){
					continue;
				}
				map.put(entry.getKey(), String.valueOf(entry.getValue()));
			}

			StringBuilder content = new StringBuilder();
			List<String> keys = new ArrayList<>(map.keySet());
			Collections.sort(keys); // 排序map
			for (int i = 0; i < keys.size(); i++) {
				String key = keys.get(i);
				String value = map.get(key);

				if(i != 0){
					content.append(SIGN_SEPARATOR);
				}
				content.append(key).append(SIGN_EQUAL).append(value);
			}
			return content.toString();
		}

		private static byte[] genMD5Sign(String signStr, String key){
			return DigestUtils.md5(signStr + SIGN_SEPARATOR + SIGN_KEY_PARAM_NAME + SIGN_EQUAL + key);
		}
	}


	/**
	 * RSA加解密的工具类
	 */
	public static class RSAUtil {
		public static final String SIGNATURE_ALGORITHM_SHA1 = "SHA1withRSA";
		public static final String SIGNATURE_ALGORITHM_MD5 = "MD5withRSA";
		public static final String RSA = "RSA";
		public static final String ANDROID_ENCRYPT_ALGORITHM = "RSA/ECB/NoPadding";
		public static final String DEFAULT_ENCRYPT_ALGORITHM = "RSA/ECB/PKCS1Padding";

		/** */
		/**
		 * RSA最大加密明文大小
		 */
		private static final int MAX_ENCRYPT_BLOCK = 116;

		/** */
		/**
		 * RSA最大解密密文大小
		 */
		private static final int MAX_DECRYPT_BLOCK = 128;

		/**
		 * 初始化BC提供者，RSA算法必须要先初始化BC才能进行加密解密和签名验签
		 */
		private static void initProvider() {
			if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
				Security.addProvider(new BouncyCastleProvider());
			}
		}

		/**
		 * 生成RSA签名串
		 *
		 * @param data       需要生成签名串的数据
		 * @param privateKey 私钥
		 * @return
		 */
		public static String sign(String data, String privateKey, boolean isSha) {
			try {
				initProvider();
				byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
				byte[] keyBytes = Base64Util.decodeFromString(privateKey);
				String algorithm = isSha ? SIGNATURE_ALGORITHM_SHA1 : SIGNATURE_ALGORITHM_MD5;

				PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
				PrivateKey priKey = KeyFactory.getInstance(RSA).generatePrivate(pkcs8KeySpec);
				Signature signature = Signature.getInstance(algorithm);
				signature.initSign(priKey);
				signature.update(dataBytes);
				return Base64Util.encodeToString(signature.sign());
			}catch (Throwable e){
				throw new ServiceException("生成RSA签名失败");
			}
		}

		/**
		 * 验证RSA签名串
		 *
		 * @param data      需要验签的数据
		 * @param publicKey 公钥
		 * @param sign      用户传过来的签名串
		 * @return
		 */
		public static boolean verify(String data, String publicKey, String sign, boolean isSha) {
			try {
				initProvider();
				byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
				byte[] signBytes = Base64Util.decodeFromString(sign);
				String algorithm = isSha ? SIGNATURE_ALGORITHM_SHA1 : SIGNATURE_ALGORITHM_MD5;
				PublicKey publicK = getPublicKey(publicKey);

				Signature signature = Signature.getInstance(algorithm);
				signature.initVerify(publicK);
				signature.update(dataBytes);
				return signature.verify(signBytes);
			} catch (Throwable e) {
				throw new ServiceException("RSA验签失败");
			}
		}

		/**
		 * 对称密钥公钥加密
		 *
		 * @param publicKeyStr 公钥Str
		 * @param content      密钥原文
		 * @return 加密密文
		 */
		public static String encryptByPublicKey(String publicKeyStr, String content) {
			byte[] dataBytes = content.getBytes(StandardCharsets.UTF_8);
			try {
				dataBytes = doCipher(dataBytes, publicKeyStr, true, false);
			} catch (Exception e) {
				throw new ServiceException("公钥加密异常:" + e.getMessage());
			}
			return Base64Util.encodeToString(dataBytes);

		}

		/**
		 * 对称密钥密文解密
		 *
		 * @param privateKeyStr 私钥字符串
		 * @param content       对称密钥密文
		 * @return 对称密钥明文
		 * @throws Exception
		 */
		public static String decryptByPrivateKey(String privateKeyStr, String content) throws Exception{
			byte[] dataBytes = Base64Util.decodeFromString(content);
			dataBytes = doCipher(dataBytes, privateKeyStr, false, false);
			return new String(dataBytes, StandardCharsets.UTF_8);

		}

		private static byte[] doCipher(byte[] dataBytes, String keyStr, boolean isEncrypt, boolean isAndroid) throws Exception {

			initProvider();

			Key key;
			Cipher cipher;
			int maxBlock;

			if (isEncrypt) {
				maxBlock = MAX_ENCRYPT_BLOCK;
				key = getPublicKey(keyStr);
				if (isAndroid) {
					cipher = Cipher.getInstance(ANDROID_ENCRYPT_ALGORITHM);// 如果是安卓机
				} else {
					cipher = Cipher.getInstance(DEFAULT_ENCRYPT_ALGORITHM);
				}
				cipher.init(Cipher.ENCRYPT_MODE, key);
			} else {
				maxBlock = MAX_DECRYPT_BLOCK;
				byte[] keyBytes = Base64Util.decodeFromString(keyStr);
				PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
				KeyFactory keyFactory = KeyFactory.getInstance(RSA);
				Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
				cipher = Cipher.getInstance(keyFactory.getAlgorithm());
				cipher.init(Cipher.DECRYPT_MODE, privateK);
			}

			int offSet = 0, i = 0, inputLen = dataBytes.length;
			byte[] cache;
			ByteArrayOutputStream out = new ByteArrayOutputStream();

			try {
				// 对数据分段加密/解密
				while (inputLen - offSet > 0) {
					if (inputLen - offSet > maxBlock) {
						cache = cipher.doFinal(dataBytes, offSet, maxBlock);
					} else {
						cache = cipher.doFinal(dataBytes, offSet, inputLen - offSet);
					}
					out.write(cache, 0, cache.length);
					i++;
					offSet = i * maxBlock;
				}
				return out.toByteArray();
			} finally {
				out.close();
			}
		}

		private static PublicKey getPublicKey(String publicKey) throws Exception {
			byte[] keyBytes = Base64Util.decodeFromString(publicKey);
			X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
			KeyFactory keyFactory = KeyFactory.getInstance(RSA);
			return keyFactory.generatePublic(x509KeySpec);
		}
	}

	/**
	 * AES加解密工具类
	 */
	public static class AESUtil {
		public static final String ALGORITHM = "AES/ECB/PKCS5Padding";
		public static final String UTF_8 = "UTF-8";
		public static final String AES = "AES";
		public static final String BC = "BC";

		static {
			//仅加载一次，不要多次加载，可能会导致内存溢出
			Security.addProvider(new BouncyCastleProvider());
		}

		/**
		 * 网联敏感字段AES加密
		 *
		 * @param str 敏感字段原文
		 * @param key AES密钥 必须是8位、16位、24位
		 * @return
		 */
		public static String Aes256Encode(String str, String key) {
			try {
				Cipher cipher = Cipher.getInstance(ALGORITHM, BC);
				SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES); // 生成加密解密需要的Key
				cipher.init(Cipher.ENCRYPT_MODE, keySpec);
				byte[] result = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
				return Base64.getEncoder().encodeToString(result);
			} catch (Exception e) {
				return null;
			}

		}

		/**
		 * 网联敏感字段密文解密
		 *
		 * @param base64Str 要被解密的密文.做了base64编码 base64Str
		 * @param key       AES密钥 必须是8位、16位、24位
		 * @return 解密后的字符串
		 */
		public static String Aes256Decode(String base64Str, String key) {
			String result = null;
			try {
				Cipher cipher = Cipher.getInstance(ALGORITHM, BC);
				SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES); // 生成加密解密需要的Key
				cipher.init(Cipher.DECRYPT_MODE, keySpec);
				byte[] decoded = cipher.doFinal(Base64Util.decodeFromString(base64Str));
				result = new String(decoded, StandardCharsets.UTF_8);
			} catch (Exception e) {
				e.printStackTrace();
			}
			return result;
		}
	}


}
